﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections.ObjectModel;

namespace Nizm0.QOT
{
    public class ActionQueue
    {
        private Thread _thread;
        private bool _isProcessed = false;
        private object _queueSync = new object();
        private readonly Queue<Action> _actions = new Queue<Action>();
        private SynchronizationContext _context;

        /// <summary>
        /// Occurs when one of executed action throws unhandled exception.
        /// </summary>
        public event CrossThreadExceptionEventHandler ExceptionOccured;

        /// <summary>
        /// Occurs when all actions in queue are finished.
        /// </summary>
        public event EventHandler ProcessingFinished;

        /// <summary>
        /// Gets enqueued actions.
        /// </summary>
        public IEnumerable<Action> Actions
        {
            get
            {
                lock (_queueSync)
                {
                    return new ReadOnlyCollection<Action>(_actions.ToList());
                }
            }
        }

        protected virtual void Execute()
        {
            _isProcessed = true;

            try
            {
                while (true)
                {
                    Action action = null;

                    lock (_queueSync)
                    {
                        if (_actions.Count == 0)
                            break;
                        else
                            action = _actions.Dequeue();
                    }

                    action.Invoke();
                }

                if (ProcessingFinished != null)
                {
                    _context.Send(s => ProcessingFinished(this, EventArgs.Empty), null);
                }
            }
            catch (ThreadAbortException)
            {
                // Execution aborted
            }
            catch (Exception ex)
            {
                if (ExceptionOccured != null)
                {
                    _context.Send(s => ExceptionOccured(this, new CrossThreadExceptionEventArgs(ex)), null);
                }
            }
            finally
            {
                _isProcessed = false;
            }
        }

        /// <summary>
        /// Starts processing current queue.
        /// </summary>
        /// <returns>Returns true if execution was started.</returns>
        public virtual bool Process()
        {
            if (!_isProcessed)
            {
                _context = SynchronizationContext.Current;

                _thread = new Thread(Execute);
                _thread.Start();

                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Enqueues action to process.
        /// </summary>
        /// <param name="action">Action to enqueue.</param>
        public void Enqueue(Action action)
        {
            lock (_queueSync)
            {
                _actions.Enqueue(action);
            }
        }

        /// <summary>
        /// Clears queue.
        /// </summary>
        public void Clear()
        {
            lock (_queueSync)
            {
                _actions.Clear();
            }
        }

        /// <summary>
        /// Aborts execution of current queue.
        /// </summary>
        /// <returns>Returns true if execution was aborted.</returns>
        public bool Abort()
        {
            if (_isProcessed)
            {
                _thread.Abort();
                return true;
            }
            else
            {
                return false;
            }
        }
    }

    public delegate void CrossThreadExceptionEventHandler(object sender, CrossThreadExceptionEventArgs e);

    public class CrossThreadExceptionEventArgs : EventArgs
    {
        public CrossThreadExceptionEventArgs(Exception exception)
        {
            this.Exception = exception;
        }

        public Exception Exception
        {
            get;
            set;
        }
    }

    interface IActionQueue
    {
        void Start();

        // if you know the list count, you can provide some progress info
        event Action<Double> ProgressChanged;

        // there is no way to catch an exception on a separate thread, so 
        // this would be neat
        event Action<Exception> ExceptionHappened;

        // this can be useful also
        event Action Finished;
    }
}
